#  utils.rb
#
#  This file is part of myvnet.
#
#  Copyright (C) 2008  Filipe David Borba Manana,  fdmanana@gmail.com
#
#  myvnet is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  myvnet is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with myvnet.  If not, see <http://www.gnu.org/licenses/>.


require 'socket'


module Utils
  
  SIZEOF_INT    = ([0].pack 'I').length
  SIZEOF_SHORT  = ([0].pack 'S').length
  
  # header bits/ioctls.h
  SIOCGIFHWADDR = 0x89_27    # get hardware address
  SIOCGIFADDR   = 0x89_15    # get PA address
  SIOCGIFMTU    = 0x89_21    # get MTU
  
  # net/if_arp.h
  ARPHRD_ETHER    = 1
  ARPHRD_LOOPBACK = 772
  
  
  module_function
  
  
  # === Synopsis
  # 
  #   Utils.if_hwinfo(interface)
  #   
  # === Args
  # 
  # A network interface name (as a String). For example: eth0, lo.
  # 
  # === Return
  # 
  # A network interface's hardware information, as a hash, or nil
  # if an error occurred while retrieving the hardware information.
  # Keys of the hash are: :hwtype, :hwaddress, :mtu
  # 
  # === Description
  # 
  # Returns hardware information of a specified network interface.
  #  
  def if_hwinfo(interface)
    hwinfo = nil    
    sock = nil
    begin
      sock = Socket.new(Socket::AF_INET, Socket::SOCK_DGRAM, 0)
      
      # buff is struct ifreq defined in net/if.h      
      buff = [interface].pack 'a16'  # ifreq.ifr_ifrn.ifrn_name[16]
      buff << ( [""].pack 'a16' )    # ifreq.ifr_ifru.ifru_hwaddr      
      sock.ioctl(SIOCGIFHWADDR, buff)
      
      z = 16 + SIZEOF_SHORT
      hwtype = hwtype = (buff[16..z].unpack 'S')[0]
      case hwtype
        when ARPHRD_ETHER
          hwtype = :ethernet
        when ARPHRD_LOOPBACK
          hwtype = :loopback
        else
          hwtype = nil
      end      
      
      hwaddr = buff[z .. (z + 6)].unpack 'C6'
      hwaddr = ("%02x:%02x:%02x:%02x:%02x:%02x" % hwaddr)
      
      buff = [interface].pack 'a16'  # ifreq.ifr_ifrn.ifrn_name[16]
      buff << ( [""].pack 'a16' )    # ifreq.ifr_ifru.ifru_mtu
      sock.ioctl(SIOCGIFMTU, buff)
      
      mtu = (buff[16 .. (16 + SIZEOF_INT)].unpack 'i')[0]      
      
      hwinfo = { :hwtype => hwtype, :hwaddress => hwaddr, :mtu => mtu }
    rescue
    ensure
      sock.close if sock
    end
    
    hwinfo
  end    
  
    
  # === Synopsis
  # 
  #   Utils.htons(hostshort)
  #   
  # === Args
  # 
  # An unsigned short value.
  # 
  # === Return
  # 
  # The unsigned short received as a paramter but in network byte order.
  # 
  # === Description
  # 
  # Converts an unsigned short from host byte order to network byte order.
  #    
  def htons(hostshort)
    s = [hostshort].pack 'S'
    (s.unpack 'n')[0]
  end
  
  
  # === Synopsis
  # 
  #   Utils.htonl(hostlong)
  #   
  # === Args
  # 
  # An unsigned integer value.
  # 
  # === Return
  # 
  # The unsigned integer received as a paramter but in network byte order.
  # 
  # === Description
  # 
  # Converts an unsigned integer from host byte order to network byte order.
  #  
  def htonl(hostlong)
    s = [hostlong].pack 'I'
    (s.unpack 'N')[0]
  end
  
  
  # === Synopsis
  # 
  #   Utils.hexdump(stream)
  #   
  # === Args
  # 
  # A byte stream, either as a String or as an Array.
  # 
  # === Return
  # 
  # An hexdump, as a String, of the given byte stream. Each line 
  # of the hex dump displays at most 16 bytes, represented as two
  # digit hexadecimal values and separated by spaces.
  # 
  # === Description
  # 
  # Returns an hexdump of a given byte stream.
  #  
  def hexdump(stream)
    case stream
      when String
      when Array
        stream = stream.pack 'C*'
      else
        return ''
    end
    
    dump = ""
    offset = -16
    cnt = 16
    
    stream.each_byte do |b|
      if cnt == 16
        offset += 16
        cnt = 0
        dump << "\n" << ("%04X" % offset) << '  '
      end
      
      dump << ("%02X" % b) << ' '
      cnt += 1
    end
    
    dump.chomp ' '
    dump << "\n"
  end
      
end
